﻿using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Threading;

namespace System.Data.SQL.Linq.Mapping
{
    #region MappingSource
    /// <summary>
    /// Represents a source for mapping information. 
    /// </summary>
    public abstract class MappingSource
    {
        private ReaderWriterLock locker;
        private Dictionary<Type, MetaModel> models;

         /// <summary> 
        /// Gets the MetaModel representing a DataContext and all it's
        /// accessible tables, functions and entities. 
        /// </summary>
        public MetaModel GetModel(Type dataContextType)
        {
            if (dataContextType == null) throw new ArgumentNullException();
            if (models == null) models = new Dictionary<Type, MetaModel>();
            if (locker == null) locker = new ReaderWriterLock();

            locker.AcquireReaderLock(Timeout.Infinite);
            try
            {
                return models[dataContextType];
            }
            catch 
            {
                var model = CreateModel(dataContextType);
                models.Add(dataContextType, model);
                return model;
            }
            finally
            {
                locker.ReleaseReaderLock();
            }
        }

        /// <summary> 
        /// Creates a new instance of a MetaModel.  This method is called by GetModel().
        /// Override this method when defining a new type of MappingSource. 
        /// </summary> 
        protected abstract MetaModel CreateModel(Type dataContextType);
    }
    #endregion

    #region MetaModel
    /// <summary>
    /// A MetaModel is an abstraction representing the mapping between a database and domain objects 
    /// </summary>
    public interface MetaModel
    {
        /// <summary> 
        ///  The mapping source that originated this model.
        /// </summary> 
        MappingSource MappingSource { get; }
        /// <summary>
        /// The type of DataContext type this model describes.
        /// </summary> 
        Type ContextType { get; }
        /// <summary> 
        /// The name of the database. 
        /// </summary>
        string DatabaseName { get; }
        /// <summary>
        /// The CLR type that implements IProvider to use as a provider.
        /// </summary>
        Type ProviderType { get; }
        /// <summary>
        /// Gets the MetaTable associated with a given type. 
        /// </summary> 
        /// <param name="rowType">The CLR row type.</param>
        /// <returns>The MetaTable if one exists, otherwise null.</returns> 
        MetaTable GetTable(Type rowType);
        /// <summary>
        /// This method discovers the MetaType for the given Type.
        /// </summary> 
        MetaType GetMetaType(Type type);
        /// <summary>
        /// Gets the MetaFunction corresponding to a database function: user-defined function, table-valued function or stored-procedure.
        /// </summary> 
        /// <param name="method">The method defined on the DataContext or subordinate class that represents the database function.</param>
        /// <returns>The MetaFunction if one exists, otherwise null.</returns> 
        MetaFunction GetFunction(MethodInfo method);
        /// <summary>
        /// Get an enumeration of all tables. 
        /// </summary>
        /// <returns>An enumeration of all the MetaTables</returns>
        IEnumerable<MetaTable> GetTables();
        /// <summary>
        /// Get an enumeration of all functions. 
        /// </summary> 
        /// <returns>An enumeration of all the MetaFunctions</returns>
        IEnumerable<MetaFunction> GetFunctions();        
    }
    #endregion

    #region MetaTable
    /// <summary> 
    /// A MetaTable represents an abstraction of a database table (or view)
    /// </summary> 
    public interface MetaTable
    {
        /// <summary>
        /// The MetaModel containing this MetaTable.
        /// </summary> 
        MetaModel Model { get; }
        /// <summary> 
        /// The name of the table as defined by the database. 
        /// </summary>
        string TableName { get; }
        /// <summary>
        /// The MetaType describing the type of the rows of the table.
        /// </summary>
        MetaType RowType { get; }
        /// <summary>
        /// The method used to perform insert operations 
        /// </summary> 
        MethodInfo InsertMethod { get; }
        /// <summary> 
        /// The method used to perform update operations
        /// </summary>
        MethodInfo UpdateMethod { get; }
        /// <summary> 
        /// The method used to perform delete operations
        /// </summary> 
        MethodInfo DeleteMethod { get; } 
    }
    #endregion

    #region MetaType
    public interface MetaType
    {
        /// <summary>
        /// The MetaModel containing this MetaType. 
        /// </summary> 
        MetaModel Model { get; }
        /// <summary> 
        /// The MetaTable using this MetaType for row definition.
        /// </summary>
        MetaTable Table { get; }
        /// <summary> 
        /// The underlying CLR type.
        /// </summary> 
        Type Type { get; }
        /// <summary> 
        /// The name of the MetaType (same as the CLR type's name).
        /// </summary>
        string Name { get; }
        /// <summary> 
        /// True if the MetaType is an entity type.
        /// </summary> 
        bool IsEntity { get; }
        /// <summary>
        /// True if the underlying type can be instantiated as the result of a query. 
        /// </summary>
        bool CanInstantiate { get; }
        /// <summary>
        /// The member that represents the auto-generated identity column, or null if there is none. 
        /// </summary>
        MetaDataMember DBGeneratedIdentityMember { get; }
        /// <summary> 
        /// The member that represents the row-version or timestamp column, or null if there is none.
        /// </summary> 
        MetaDataMember VersionMember { get; }
        /// <summary>
        /// The member that represents the inheritance discriminator column, or null if there is none.
        /// </summary> 
        MetaDataMember Discriminator { get; }
        /// <summary> 
        /// True if the type has any persistent member with an UpdateCheck policy other than Never. 
        /// </summary>
        bool HasUpdateCheck { get; }
        /// <summary>
        /// True if the type is part of a mapped inheritance hierarchy.
        /// </summary>
        bool HasInheritance { get; }
        /// <summary>
        /// True if this type defines an inheritance code. 
        /// </summary> 
        bool HasInheritanceCode { get; }
        /// <summary> 
        /// The inheritance code defined by this type.
        /// </summary>
        object InheritanceCode { get; }
        /// <summary> 
        /// True if this type is used as the default of an inheritance hierarchy.
        /// </summary> 
        bool IsInheritanceDefault { get; }
        /// <summary>
        /// The root type of the inheritance hierarchy. 
        /// </summary>
        MetaType InheritanceRoot { get; }
        /// <summary>
        /// The base metatype in the inheritance hierarchy. 
        /// </summary>
        MetaType InheritanceBase { get; }
        /// <summary> 
        /// The type that is the default of the inheritance hierarchy.
        /// </summary> 
        MetaType InheritanceDefault { get; }
        /// <summary>
        /// Gets the MetaType for an inheritance sub type.
        /// </summary> 
        /// <param name="type">The root or sub type of the inheritance hierarchy.</param>
        /// <returns>The MetaType.</returns> 
        MetaType GetInheritanceType(Type type);
        /// <summary>
        /// Gets type associated with the specified inheritance code. 
        /// </summary>
        /// <param name="code">The inheritance code</param>
        /// <returns>The MetaType.</returns>
        MetaType GetTypeForInheritanceCode(object code);
        /// <summary>
        /// Gets an enumeration of all types defined by an inheritance hierarchy. 
        /// </summary> 
        /// <returns>Enumeration of MetaTypes.</returns>
        ReadOnlyCollection<MetaType> InheritanceTypes { get; }
        /// <summary>
        /// Returns true if the MetaType or any base MetaType has an OnLoaded method.
        /// </summary>
        bool HasAnyLoadMethod { get; }
        /// <summary>
        /// Returns true if the MetaType or any base MetaType has an OnValidate method. 
        /// </summary> 
        bool HasAnyValidateMethod { get; }
        /// <summary> 
        /// Gets an enumeration of the immediate derived types in an inheritance hierarchy.
        /// </summary>
        /// <returns>Enumeration of MetaTypes.</returns>
        ReadOnlyCollection<MetaType> DerivedTypes { get; }
        /// <summary>
        /// Gets an enumeration of all the data members (fields and properties). 
        /// </summary> 
        ReadOnlyCollection<MetaDataMember> DataMembers { get; }
        /// <summary> 
        /// Gets an enumeration of all the persistent data members (fields and properties mapped into database columns).
        /// </summary>
        ReadOnlyCollection<MetaDataMember> PersistentDataMembers { get; }
        /// <summary> 
        /// Gets an enumeration of all the data members that define up the unique identity of the type.
        /// </summary> 
        ReadOnlyCollection<MetaDataMember> IdentityMembers { get; }
        /// <summary>
        /// Gets an enumeration of all the associations. 
        /// </summary>
        ReadOnlyCollection<MetaAssociation> Associations { get; }
        /// <summary>
        /// Gets the MetaDataMember associated with the specified member. 
        /// </summary>
        /// <param name="member">The CLR member.</param> 
        /// <returns>The MetaDataMember if there is one, otherwise null.</returns> 
        MetaDataMember GetDataMember(MemberInfo member);
        /// <summary> 
        /// The method called when the entity is first loaded.
        /// </summary>
        MethodInfo OnLoadedMethod { get; }
        /// <summary> 
        /// The method called to ensure the entity is in a valid state.
        /// </summary> 
        MethodInfo OnValidateMethod { get; } 
    }
    #endregion

    #region MetaDataMember
    /// <summary>
    /// A MetaDataMember represents the mapping between a domain object's field or property into a database table's column.
    /// </summary>
    public interface MetaDataMember
    {
        /// <summary> 
        /// The MetaType containing this data member. 
        /// </summary>
        MetaType DeclaringType { get; }
        /// <summary>
        /// The underlying MemberInfo.
        /// </summary>
        MemberInfo Member { get; }
        /// <summary>
        /// The member that actually stores this member's data. 
        /// </summary> 
        MemberInfo StorageMember { get; }
        /// <summary> 
        /// The name of the member, same as the MemberInfo name.
        /// </summary>
        string Name { get; }
        /// <summary> 
        /// The name of the column (or constraint) in the database.
        /// </summary> 
        string MappedName { get; }
        /// <summary>
        /// The oridinal position of this member in the default layout of query results. 
        /// </summary>
        int Ordinal { get; }
        /// <summary>
        /// The type of this member. 
        /// </summary>
        Type Type { get; }
        /// <summary>
        /// True if this member is declared by the specified type. 
        /// </summary>
        /// <param name="type">Type to check.</param>
        bool IsDeclaredBy(MetaType type);
        /// <summary> 
        /// The accessor used to get/set the value of this member.
        /// </summary> 
        MetaAccessor MemberAccessor { get; }
        /// <summary>
        /// The accessor used to get/set the storage value of this member. 
        /// </summary>
        MetaAccessor StorageAccessor { get; }
        /// <summary>
        /// The accessor used to get/set the deferred value of this member (without causing fetch). 
        /// </summary>
        MetaAccessor DeferredValueAccessor { get; }
        /// <summary> 
        /// The accessor used to get/set the deferred source of this member.
        /// </summary> 
        MetaAccessor DeferredSourceAccessor { get; }
        /// <summary>
        /// True if this member is defer-loaded by default.
        /// </summary> 
        bool IsDeferred { get; }
        /// <summary> 
        /// True if this member is mapped to a column (or constraint). 
        /// </summary>
        bool IsPersistent { get; }
        /// <summary>
        /// True if this member defines an association relationship.
        /// </summary>
        bool IsAssociation { get; }
        /// <summary>
        /// True if this member is part of the type's identity. 
        /// </summary> 
        bool IsPrimaryKey { get; }
        /// <summary> 
        /// True if this member is automatically generated by the database.
        /// </summary>
        bool IsDbGenerated { get; }
        /// <summary>
        /// True if this member represents the row version or timestamp. 
        /// </summary> 
        bool IsVersion { get; }
        /// <summary> 
        /// True if this member represents the inheritance discriminator.
        /// </summary>
        bool IsDiscriminator { get; }
        /// <summary> 
        /// True if this member's value can be assigned the null value.
        /// </summary> 
        bool CanBeNull { get; }
        /// <summary>
        /// The type of the database column. 
        /// </summary>
        string DbType { get; }
        /// <summary> 
        /// Expression defining a computed column.
        /// </summary> 
        string Expression { get; }
        /// <summary>
        /// The optimistic concurrency check policy for this member. 
        /// </summary>
        UpdateCheck UpdateCheck { get; }
        /// <summary>
        /// Specifies for inserts and updates when this member should be read back after the 
        /// operation completes.
        /// </summary> 
        AutoSync AutoSync { get; }
        /// <summary>
        /// The MetaAssociation corresponding to this member, or null if there is none. 
        /// </summary>
        MetaAssociation Association { get; }
        /// <summary>
        /// The DataContext method used to perform load operations 
        /// </summary>
        MethodInfo LoadMethod { get; } 
    }
    #endregion

    #region MetaFunction
    /// <summary> 
    /// A MetaFunction represents the mapping between a context method and a database function.
    /// </summary>
    public interface MetaFunction
    {
        /// <summary> 
        /// The MetaModel containing this function.
        /// </summary> 
        MetaModel Model { get; }
        /// <summary>
        /// The underlying context method. 
        /// </summary>
        MethodInfo Method { get; }
        /// <summary>
        /// The name of the method (same as the MethodInfo's name). 
        /// </summary>
        string Name { get; }
        /// <summary> 
        /// The name of the database function or procedure.
        /// </summary> 
        string MappedName { get; }
        /// <summary>
        /// True if the function can be composed within a query
        /// </summary> 
        bool IsComposable { get; }
        /// <summary> 
        /// Gets an enumeration of the function parameters.
        /// </summary> 
        /// <returns></returns>
        ReadOnlyCollection<MetaParameter> Parameters { get; }
        /// <summary>
        /// The return parameter 
        /// </summary>
        MetaParameter ReturnParameter { get; }
        /// <summary> 
        /// True if the stored procedure has multiple result types.
        /// </summary> 
        bool HasMultipleResults { get; }
        /// <summary>
        /// An enumeration of all the known result row types of a stored-procedure.
        /// </summary> 
        /// <returns>Enumeration of possible result row types.</returns>
        ReadOnlyCollection<MetaType> ResultRowTypes { get; } 
    }
    #endregion

    #region MetaParameter
    /// <summary> 
    /// A MetaParameter represents the mapping between a method parameter and a database function parameter.
    /// </summary>
    public interface MetaParameter
    {
        /// <summary> 
        /// The underlying method parameter.
        /// </summary> 
        ParameterInfo Parameter { get; }
        /// <summary>
        /// The name of the parameter (same as the ParameterInfo's name). 
        /// </summary>
        string Name { get; }
        /// <summary>
        /// The name of the database function's parameter. 
        /// </summary>
        string MappedName { get; }
        /// <summary> 
        /// The CLR type of the parameter.
        /// </summary> 
        Type ParameterType { get; }
        /// <summary>
        /// The database type of the parameter.
        /// </summary> 
        string DbType { get; }
    } 
    #endregion

    #region MetaAssociation
    /// <summary> 
    /// A MetaAssociation represents an association relationship between two entity types.
    /// </summary>
    public interface MetaAssociation
    {
        /// <summary> 
        /// The type on the other end of the association.
        /// </summary> 
        MetaType OtherType { get; }
        /// <summary>
        /// The member on this side that represents the association. 
        /// </summary>
        MetaDataMember ThisMember { get; }
        /// <summary>
        /// The member on the other side of this association that represents the reverse association (may be null). 
        /// </summary>
        MetaDataMember OtherMember { get; }
        /// <summary> 
        /// A list of members representing the values on this side of the association.
        /// </summary> 
        ReadOnlyCollection<MetaDataMember> ThisKey { get; }
        /// <summary>
        /// A list of members representing the values on the other side of the association.
        /// </summary> 
        ReadOnlyCollection<MetaDataMember> OtherKey { get; }
        /// <summary> 
        /// True if the association is OneToMany. 
        /// </summary>
        bool IsMany { get; }
        /// <summary>
        /// True if the other type is the parent of this type.
        /// </summary>
        bool IsForeignKey { get; }
        /// <summary>
        /// True if the association is unique (defines a uniqueness constraint). 
        /// </summary> 
        bool IsUnique { get; }
        /// <summary> 
        /// True if the association may be null (key values).
        /// </summary>
        bool IsNullable { get; }
        /// <summary> 
        /// True if the ThisKey forms the identity (primary key) of the this type.
        /// </summary> 
        bool ThisKeyIsPrimaryKey { get; }
        /// <summary>
        /// True if the OtherKey forms the identity (primary key) of the other type. 
        /// </summary>
        bool OtherKeyIsPrimaryKey { get; }
        /// <summary>
        /// Specifies the behavior when the child is deleted (e.g. CASCADE, SET NULL). 
        /// Returns null if no action is specified on delete.
        /// </summary> 
        string DeleteRule { get; }
        /// <summary>
        /// Specifies whether the object should be deleted when this association 
        /// is set to null.
        /// </summary>
        bool DeleteOnNull { get; }
    }
    #endregion

    #region MetaAccessor
    /// <summary> 
    /// A MetaAccessor 
    /// </summary>
    public abstract class MetaAccessor
    {
        /// <summary>
        /// The type of the member accessed by this accessor.
        /// </summary>
        public Type Type { get; private set; }

        protected MetaAccessor(Type type)
        {
            Type = type;
        }

        /// <summary> 
        /// Gets the value as an object. 
        /// </summary>
        /// <param name="instance">The instance to get the value from.</param> 
        /// <returns>Value.</returns>
        public abstract object GetBoxedValue(object instance);
        /// <summary>
        /// Sets the value as an object. 
        /// </summary>
        /// <param name="instance">The instance to set the value into.</param> 
        /// <param name="value">The value to set.</param> 
        public abstract void SetBoxedValue(ref object instance, object value);
        /// <summary>
        /// True if the instance has a loaded or assigned value.
        /// </summary> 
        public virtual bool HasValue(object instance)
        {
            return true;
        }
        /// <summary>
        /// True if the instance has an assigned value. 
        /// </summary>
        public virtual bool HasAssignedValue(object instance)
        {
            return true;
        }
        /// <summary>
        /// True if the instance has a value loaded from a deferred source. 
        /// </summary> 
        public virtual bool HasLoadedValue(object instance)
        {
            return false;
        }
    }
    #endregion

    #region MetaAccessor<TEntity, TMember>
    /// <summary> 
    /// A strongly-typed MetaAccessor. Used for reading from and writing to
    /// CLR objects. 
    /// </summary> 
    /// <typeparam name="T">The type of the object</typeparam>
    /// <typeparam name="V">The type of the accessed member</typeparam> 
    public abstract class MetaAccessor<TEntity, TMember> : MetaAccessor
    {
        protected MetaAccessor()
            : base(typeof(TMember))
        {
        }

        /// <summary>
        /// Set the boxed value on an instance. 
        /// </summary>
        public override void SetBoxedValue(ref object instance, object value)
        {
            TEntity tInst = (TEntity)instance;
            this.SetValue(ref tInst, (TMember)value);
            instance = tInst;
        }
        /// <summary> 
        /// Retrieve the boxed value.
        /// </summary> 
        public override object GetBoxedValue(object instance)
        {
            return this.GetValue((TEntity)instance);
        }
        /// <summary> 
        /// Gets the strongly-typed value.
        /// </summary> 
        public abstract TMember GetValue(TEntity instance);
        /// <summary>
        /// Sets the strongly-typed value 
        /// </summary>
        public abstract void SetValue(ref TEntity instance, TMember value);
    } 
    #endregion

    #region MetaPosition
    /// <summary>
    /// Hashable MetaDataToken+Assembly. This type uniquely describes a metadata element
    /// like a MemberInfo. MetaDataToken by itself is not sufficient because its only
    /// unique within a single assembly. 
    /// </summary>
    public sealed class MetaPosition : IEqualityComparer<MetaPosition>, IEqualityComparer
    {
        public int MetadataToken { get; private set; }
        public Assembly Assembly { get; private set; }

        public MetaPosition(MemberInfo mi)
            : this(mi.DeclaringType.Assembly, mi.MetadataToken)
        {
        }
        public MetaPosition(Assembly assembly, int metadataToken)
        {
            Assembly = assembly;
            MetadataToken = metadataToken;
        }

        // Since MetaPositions are immutable, we overload the equality operator 
        // to test for value equality, rather than reference equality 
        public static bool operator ==(MetaPosition x, MetaPosition y)
        {
            return AreEqual(x, y);
        }
        public static bool operator !=(MetaPosition x, MetaPosition y)
        {
            return !AreEqual(x, y);
        }

        public static bool AreEqual(MetaPosition x, MetaPosition y)
        {
            return (x.MetadataToken == y.MetadataToken) && (x.Assembly == y.Assembly);
        }

        public static bool AreSameMember(MemberInfo x, MemberInfo y)
        {
            if (x.MetadataToken != y.MetadataToken || x.DeclaringType.Assembly != y.DeclaringType.Assembly) return false;
            return true;
        }

        public override bool Equals(object obj)
        {
            if (obj == null) return false;

            if (obj.GetType() != this.GetType()) return false;

            return AreEqual(this, (MetaPosition)obj);
        }
        public override int GetHashCode()
        {
            return MetadataToken;
        }

        public bool Equals(MetaPosition x, MetaPosition y)
        {
            return AreEqual(x, y);
        }

        public int GetHashCode(MetaPosition obj)
        {
            return obj.MetadataToken;
        }

        bool IEqualityComparer.Equals(object x, object y)
        {
            return this.Equals((MetaPosition)x, (MetaPosition)y);
        }

        public int GetHashCode(object obj)
        {
            return this.GetHashCode((MetaPosition)obj);
        }
    }
    #endregion
}
